/* -*- Mode: C; tab-width: 4 -*-
 *
 * Copyright (c) 2002-2003 Apple Computer, Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <stdio.h>
#include <stdarg.h>                     // For va_list support

#include <LowMem.h>                     // For LMGetCurApName()
#include <TextUtils.h>                  // For smSystemScript
#include <UnicodeConverter.h>           // For ConvertFromPStringToUnicode()

#include "mDNSEmbeddedAPI.h"                // Defines the interface provided to the client layer above

#include "mDNSMacOS9.h"                 // Defines the specific types needed to run mDNS on this platform

// ***************************************************************************
// Constants

static const TSetBooleanOption kReusePortOption =
{ kOTBooleanOptionSize,          INET_IP, IP_REUSEPORT,      0, true };

// IP_RCVDSTADDR with TSetByteOption/kOTOneByteOptionSize works on OS 9, OS X Classic, and OS 9 Carbon,
// but gives error #-3151 (kOTBadOptionErr) on OS X Carbon.
// If we instead use TSetBooleanOption/kOTBooleanOptionSize then OTOptionManagement on OS X Carbon
// no longer returns -3151 but it still doesn't actually work -- no destination addresses
// are delivered by OTRcvUData. I think it's just a bug in OS X Carbon.
static const TSetByteOption kRcvDestAddrOption =
{ kOTOneByteOptionSize,          INET_IP, IP_RCVDSTADDR,     0, true };
//static const TSetBooleanOption kRcvDestAddrOption =
//	{ kOTBooleanOptionSize,          INET_IP, IP_RCVDSTADDR,     0, true };

static const TSetByteOption kSetUnicastTTLOption =
{ kOTOneByteOptionSize,          INET_IP, IP_TTL,            0, 255 };

static const TSetByteOption kSetMulticastTTLOption =
{ kOTOneByteOptionSize,          INET_IP, IP_MULTICAST_TTL,  0, 255 };

static const TIPAddMulticastOption kAddLinkMulticastOption  =
{ sizeof(TIPAddMulticastOption), INET_IP, IP_ADD_MEMBERSHIP, 0, { 224,  0,  0,251 }, { 0,0,0,0 } };

//static const TIPAddMulticastOption kAddAdminMulticastOption =
//	{ sizeof(TIPAddMulticastOption), INET_IP, IP_ADD_MEMBERSHIP, 0, { 239,255,255,251 }, { 0,0,0,0 } };

// Bind endpoint to port number. Don't specify any specific IP address --
// we want to receive unicasts on all interfaces, as well as multicasts.
typedef struct { OTAddressType fAddressType; mDNSIPPort fPort; mDNSv4Addr fHost; UInt8 fUnused[8]; } mDNSInetAddress;
//static const mDNSInetAddress mDNSPortInetAddress = { AF_INET, { 0,0 }, { 0,0,0,0 } };	// For testing legacy client support
#define MulticastDNSPortAsNumber 5353
static const mDNSInetAddress mDNSPortInetAddress = { AF_INET, { MulticastDNSPortAsNumber >> 8, MulticastDNSPortAsNumber & 0xFF }, { 0,0,0,0 } };
static const TBind mDNSbindReq = { sizeof(mDNSPortInetAddress), sizeof(mDNSPortInetAddress), (UInt8*)&mDNSPortInetAddress, 0 };

static const TNetbuf zeroTNetbuf = { 0 };

// ***************************************************************************
// Functions

mDNSlocal void SafeDebugStr(unsigned char *buffer)
{
    int i;
    // Don't want semicolons in MacsBug messages -- they signify commands to execute
    for (i=1; i<= buffer[0]; i++) if (buffer[i] == ';') buffer[i] = '.';
    DebugStr(buffer);
}

#if MDNS_DEBUGMSGS
mDNSexport void debugf_(const char *format, ...)
{
    unsigned char buffer[256];
    va_list ptr;
    va_start(ptr,format);
    buffer[0] = (unsigned char)mDNS_vsnprintf((char*)buffer+1, 255, format, ptr);
    va_end(ptr);
#if MDNS_ONLYSYSTEMTASK
    buffer[1+buffer[0]] = 0;
    fprintf(stderr, "%s\n", buffer+1);
    fflush(stderr);
#else
    SafeDebugStr(buffer);
#endif
}
#endif

#if MDNS_BUILDINGSHAREDLIBRARY >= 2
// When building the non-debug version of the Extension, intended to go on end-user systems, we don't want
// MacsBug breaks for *anything*, not even for the serious LogMsg messages that on OS X would be written to syslog
mDNSexport void LogMsg(const char *format, ...) { (void)format; }
#else
mDNSexport void LogMsg(const char *format, ...)
{
    unsigned char buffer[256];
    va_list ptr;
    va_start(ptr,format);
    buffer[0] = (unsigned char)mDNS_vsnprintf((char*)buffer+1, 255, format, ptr);
    va_end(ptr);
#if MDNS_ONLYSYSTEMTASK
    buffer[1+buffer[0]] = 0;
    fprintf(stderr, "%s\n", buffer+1);
    fflush(stderr);
#else
    SafeDebugStr(buffer);
#endif
}
#endif

mDNSexport mStatus mDNSPlatformSendUDP(const mDNS *const m, const void *const msg, const mDNSu8 *const end,
                                       mDNSInterfaceID InterfaceID, const mDNSAddr *dst, mDNSIPPort dstPort)
{
    // Note: If we did multi-homing, we'd have to use the InterfaceID parameter to specify from which interface to send this response
    #pragma unused(InterfaceID)

    InetAddress InetDest;
    TUnitData senddata;

    if (dst->type != mDNSAddrType_IPv4) return(mStatus_NoError);

    InetDest.fAddressType = AF_INET;
    InetDest.fPort        = dstPort.NotAnInteger;
    InetDest.fHost        = dst->ip.v4.NotAnInteger;

    senddata.addr.maxlen = sizeof(InetDest);
    senddata.addr.len    = sizeof(InetDest);
    senddata.addr.buf    = (UInt8*)&InetDest;
    senddata.opt          = zeroTNetbuf;
    senddata.udata.maxlen = (UInt32)((UInt8*)end - (UInt8*)msg);
    senddata.udata.len    = (UInt32)((UInt8*)end - (UInt8*)msg);
    senddata.udata.buf    = (UInt8*)msg;

    return(OTSndUData(m->p->ep, &senddata));
}

mDNSlocal OSStatus readpacket(mDNS *m)
{
    mDNSAddr senderaddr, destaddr;
    mDNSInterfaceID interface;
    mDNSIPPort senderport;
    InetAddress sender;
    char options[256];
    DNSMessage packet;
    TUnitData recvdata;
    OTFlags flags = 0;
    OSStatus err;

    recvdata.addr.maxlen = sizeof(sender);
    recvdata.addr.len    = 0;
    recvdata.addr.buf    = (UInt8*)&sender;
    recvdata.opt.maxlen = sizeof(options);
    recvdata.opt.len    = 0;
    recvdata.opt.buf    = (UInt8*)&options;
    recvdata.udata.maxlen = sizeof(packet);
    recvdata.udata.len    = 0;
    recvdata.udata.buf    = (UInt8*)&packet;

    err = OTRcvUData(m->p->ep, &recvdata, &flags);
    if (err && err != kOTNoDataErr) debugf("OTRcvUData error %d", err);

    if (err) return(err);

    senderaddr.type = mDNSAddrType_IPv4;
    senderaddr.ip.v4.NotAnInteger = sender.fHost;
    senderport.NotAnInteger = sender.fPort;

    destaddr.type = mDNSAddrType_IPv4;
    destaddr.ip.v4  = zerov4Addr;

    #if OTCARBONAPPLICATION
    // IP_RCVDSTADDR is known to fail on OS X Carbon, so we'll just assume the packet was probably multicast
    destaddr.ip.v4  = AllDNSLinkGroup_v4.ip.v4;
    #endif

    if (recvdata.opt.len)
    {
        TOption *c = nil;
        while (1)
        {
            err = OTNextOption(recvdata.opt.buf, recvdata.opt.len, &c);
            if (err || !c) break;
            if (c->level == INET_IP && c->name == IP_RCVDSTADDR && c->len - kOTOptionHeaderSize == sizeof(destaddr.ip.v4))
                mDNSPlatformMemCopy(&destaddr.ip.v4, c->value, sizeof(destaddr.ip.v4));
        }
    }

    interface = m->HostInterfaces->InterfaceID;

    if      (flags & T_MORE) debugf("ERROR: OTRcvUData() buffer too small (T_MORE set)");
    else if (recvdata.addr.len < sizeof(InetAddress)) debugf("ERROR: recvdata.addr.len (%d) too short", recvdata.addr.len);
    else mDNSCoreReceive(m, &packet, recvdata.udata.buf + recvdata.udata.len, &senderaddr, senderport, &destaddr, MulticastDNSPort, interface);

    return(err);
}

mDNSexport TCPSocket *mDNSPlatformTCPSocket(mDNS * const m, TCPSocketFlags flags, mDNSIPPort * port)
{
    (void)m;            // Unused
    (void)flags;        // Unused
    (void)port;         // Unused
    return NULL;
}

mDNSexport TCPSocket *mDNSPlatformTCPAccept(TCPSocketFlags flags, int sd)
{
    (void)flags;        // Unused
    (void)sd;           // Unused
    return NULL;
}

mDNSexport int mDNSPlatformTCPGetFD(TCPSocket *sock)
{
    (void)sock;         // Unused
    return -1;
}

mDNSexport mStatus mDNSPlatformTCPConnect(TCPSocket *sock, const mDNSAddr * dst, mDNSOpaque16 dstport, mDNSInterfaceID InterfaceID,
                                          TCPConnectionCallback callback, void * context)
{
    (void)sock;         // Unused
    (void)dst;          // Unused
    (void)dstport;      // Unused
    (void)InterfaceID;  // Unused
    (void)callback;     // Unused
    (void)context;      // Unused
    return(mStatus_UnsupportedErr);
}

mDNSexport void mDNSPlatformTCPCloseConnection(TCPSocket *sd)
{
    (void)sd;           // Unused
}

mDNSexport long mDNSPlatformReadTCP(TCPSocket *sock, void *buf, unsigned long buflen, mDNSBool *closed)
{
    (void)sock;         // Unused
    (void)buf;          // Unused
    (void)buflen;       // Unused
    (void)closed;       // Unused
    return(0);
}

mDNSexport long mDNSPlatformWriteTCP(TCPSocket *sock, const char *msg, unsigned long len)
{
    (void)sock;         // Unused
    (void)msg;          // Unused
    (void)len;          // Unused
    return(0);
}

mDNSexport UDPSocket *mDNSPlatformUDPSocket(mDNS * const m, mDNSIPPort port)
{
    (void)m;            // Unused
    (void)port;         // Unused
    return NULL;
}

mDNSexport void           mDNSPlatformUDPClose(UDPSocket *sock)
{
    (void)sock;         // Unused
}

mDNSlocal void mDNSOptionManagement(mDNS *const m)
{
    OSStatus err;

    // Make sure the length in the TNetbuf agrees with the length in the TOptionHeader
    m->p->optReq.opt.len    = m->p->optBlock.h.len;
    m->p->optReq.opt.maxlen = m->p->optBlock.h.len;
    if (m->p->optReq.opt.maxlen < 4)
        m->p->optReq.opt.maxlen = 4;

    err = OTOptionManagement(m->p->ep, &m->p->optReq, NULL);
    if (err) LogMsg("OTOptionManagement failed %d", err);
}

mDNSlocal void mDNSinitComplete(mDNS *const m, mStatus result)
{
    m->mDNSPlatformStatus = result;
    mDNSCoreInitComplete(m, mStatus_NoError);
}

mDNSlocal pascal void mDNSNotifier(void *contextPtr, OTEventCode code, OTResult result, void *cookie)
{
    mDNS *const m = (mDNS *const)contextPtr;
    if (!m) debugf("mDNSNotifier FATAL ERROR! No context");
    switch (code)
    {
    case T_OPENCOMPLETE:
    {
        OSStatus err;
        InetInterfaceInfo interfaceinfo;
        if (result) { LogMsg("T_OPENCOMPLETE failed %d", result); mDNSinitComplete(m, result); return; }
        //debugf("T_OPENCOMPLETE");
        m->p->ep = (EndpointRef)cookie;
        //debugf("OTInetGetInterfaceInfo");
        // (In future may want to loop over all interfaces instead of just using kDefaultInetInterface)
        err = OTInetGetInterfaceInfo(&interfaceinfo, kDefaultInetInterface);
        if (err) { LogMsg("OTInetGetInterfaceInfo failed %d", err); mDNSinitComplete(m, err); return; }

        // Make our basic standard host resource records (address, PTR, etc.)
        m->p->interface.InterfaceID             = (mDNSInterfaceID)&m->p->interface;
        m->p->interface.ip.type               = mDNSAddrType_IPv4;
        m->p->interface.ip.ip.v4.NotAnInteger = interfaceinfo.fAddress;
        m->p->interface.mask.type               = mDNSAddrType_IPv4;
        m->p->interface.mask.ip.v4.NotAnInteger = interfaceinfo.fNetmask;
        m->p->interface.ifname[0]               = 0;
        m->p->interface.Advertise               = m->AdvertiseLocalAddresses;
        m->p->interface.McastTxRx               = mDNStrue;
    }

    case T_OPTMGMTCOMPLETE:
    case T_BINDCOMPLETE:
        // IP_RCVDSTADDR is known to fail on OS X Carbon, so we don't want to abort for that error
        // (see comment above at the definition of kRcvDestAddrOption)
            #if OTCARBONAPPLICATION
        if (result && m->p->mOTstate == mOT_RcvDestAddr)
            LogMsg("Carbon IP_RCVDSTADDR option failed %d; continuing anyway", result);
        else
            #endif
        if (result) { LogMsg("T_OPTMGMTCOMPLETE/T_BINDCOMPLETE %d failed %d", m->p->mOTstate, result); mDNSinitComplete(m, result); return; }
        //LogMsg("T_OPTMGMTCOMPLETE/T_BINDCOMPLETE %d", m->p->mOTstate);
        switch (++m->p->mOTstate)
        {
        case mOT_ReusePort:     m->p->optBlock.b = kReusePortOption;         mDNSOptionManagement(m); break;
        case mOT_RcvDestAddr:   m->p->optBlock.i = kRcvDestAddrOption;       mDNSOptionManagement(m); break;
        case mOT_SetUTTL:       m->p->optBlock.i = kSetUnicastTTLOption;     mDNSOptionManagement(m); break;
        case mOT_SetMTTL:       m->p->optBlock.i = kSetMulticastTTLOption;   mDNSOptionManagement(m); break;
        case mOT_LLScope:       m->p->optBlock.m = kAddLinkMulticastOption;  mDNSOptionManagement(m); break;
//				case mOT_AdminScope:	m->p->optBlock.m = kAddAdminMulticastOption; mDNSOptionManagement(m); break;
        case mOT_Bind:          OTBind(m->p->ep, (TBind*)&mDNSbindReq, NULL); break;
        case mOT_Ready:         mDNSinitComplete(m, mStatus_NoError);
            // Can't do mDNS_RegisterInterface until *after* mDNSinitComplete has set m->mDNSPlatformStatus to mStatus_NoError
            mDNS_RegisterInterface(m, &m->p->interface, mDNSfalse);
            break;
        default:                LogMsg("Unexpected m->p->mOTstate %d", m->p->mOTstate-1);
        }
        break;

    case T_DATA:
        //debugf("T_DATA");
        while (readpacket(m) == kOTNoError) continue;       // Read packets until we run out
        break;

    case kOTProviderWillClose: LogMsg("kOTProviderWillClose"); break;
    case kOTProviderIsClosed:           // Machine is going to sleep, shutting down, or reconfiguring IP
        LogMsg("kOTProviderIsClosed");
        if (m->p->mOTstate == mOT_Ready)
        {
            m->p->mOTstate = mOT_Closed;
            mDNS_DeregisterInterface(m, &m->p->interface, mDNSfalse);
        }
        if (m->p->ep) { OTCloseProvider(m->p->ep); m->p->ep = NULL; }
        break;                          // Do we need to do anything?

    default: debugf("mDNSNotifier: Unexpected OTEventCode %X", code);
        break;
    }
}

#if MDNS_ONLYSYSTEMTASK

static Boolean ONLYSYSTEMTASKevent;
static void       *ONLYSYSTEMTASKcontextPtr;
static OTEventCode ONLYSYSTEMTASKcode;
static OTResult ONLYSYSTEMTASKresult;
static void       *ONLYSYSTEMTASKcookie;

mDNSlocal pascal void CallmDNSNotifier(void *contextPtr, OTEventCode code, OTResult result, void *cookie)
{
    ONLYSYSTEMTASKcontextPtr = contextPtr;
    ONLYSYSTEMTASKcode       = code;
    ONLYSYSTEMTASKresult     = result;
    ONLYSYSTEMTASKcookie     = cookie;
}

#else

mDNSlocal pascal void CallmDNSNotifier(void *contextPtr, OTEventCode code, OTResult result, void *cookie)
{
    mDNS *const m = (mDNS *const)contextPtr;
    if (!m) debugf("mDNSNotifier FATAL ERROR! No context");
    if (m->p->nesting) LogMsg("CallmDNSNotifier ERROR! OTEnterNotifier is supposed to suppress notifier callbacks");
    mDNSNotifier(contextPtr, code, result, cookie);
}

#endif

static OTNotifyUPP CallmDNSNotifierUPP;

mDNSlocal OSStatus mDNSOpenEndpoint(const mDNS *const m)
{
    OSStatus err;
    // m->optReq is pre-set to point to the shared m->optBlock
    // m->optBlock is filled in by each OTOptionManagement call
    m->p->optReq.opt.maxlen = sizeof(m->p->optBlock);
    m->p->optReq.opt.len    = sizeof(m->p->optBlock);
    m->p->optReq.opt.buf    = (UInt8*)&m->p->optBlock;
    m->p->optReq.flags      = T_NEGOTIATE;

    // Open an endpoint and start answering queries
    //printf("Opening endpoint now...\n");
    m->p->ep = NULL;
    m->p->mOTstate = mOT_Start;
    err = OTAsyncOpenEndpoint(OTCreateConfiguration(kUDPName), 0, NULL, CallmDNSNotifierUPP, (void*)m);
    if (err) { LogMsg("ERROR: OTAsyncOpenEndpoint(UDP) failed with error <%d>", err); return(err); }
    return(kOTNoError);
}

// Define these here because they're not in older versions of OpenTransport.h
enum
{
    xOTStackIsLoading   = 0x27000001,   /* Sent before Open Transport attempts to load the TCP/IP protocol stack.*/
    xOTStackWasLoaded   = 0x27000002,   /* Sent after the TCP/IP stack has been successfully loaded.*/
    xOTStackIsUnloading = 0x27000003    /* Sent before Open Transport unloads the TCP/IP stack.*/
};

static mDNS *ClientNotifierContext;

mDNSlocal pascal void ClientNotifier(void *contextPtr, OTEventCode code, OTResult result, void *cookie)
{
    mDNS *const m = ClientNotifierContext;

    #pragma unused(contextPtr) // Usually zero (except one in the 'xOTStackIsLoading' case)
    #pragma unused(cookie) // Usually 'ipv4' (except for kOTPortNetworkChange)
    #pragma unused(result) // Usually zero

    switch (code)
    {
    case xOTStackIsLoading:     break;
    case xOTStackWasLoaded:     if (m->p->mOTstate == mOT_Closed)
        {
            LogMsg("kOTStackWasLoaded: Re-opening endpoint");
            if (m->p->ep)
                LogMsg("kOTStackWasLoaded: ERROR: m->p->ep already set");
            m->mDNSPlatformStatus = mStatus_Waiting;
            m->p->mOTstate = mOT_Reset;
                                        #if !MDNS_ONLYSYSTEMTASK
            mDNSOpenEndpoint(m);
                                        #endif
        }
        else
            LogMsg("kOTStackWasLoaded (no action)");
        break;
    case xOTStackIsUnloading:   break;
    case kOTPortNetworkChange:  break;
    default: debugf("ClientNotifier unknown code %X, %X, %d", contextPtr, code, result); break;
    }
}

#if TARGET_API_MAC_CARBON

mDNSlocal void GetUserSpecifiedComputerName(domainlabel *const namelabel)
{
    CFStringRef cfs = CSCopyMachineName();
    CFStringGetPascalString(cfs, namelabel->c, sizeof(*namelabel), kCFStringEncodingUTF8);
    CFRelease(cfs);
}

#else

mDNSlocal OSStatus ConvertStringHandleToUTF8(const StringHandle machineName, UInt8 *const utf8, ByteCount maxlen)
{
    OSStatus status;
    TextEncoding utf8TextEncoding, SystemTextEncoding;
    UnicodeMapping theMapping;
    TextToUnicodeInfo textToUnicodeInfo;
    ByteCount unicodelen = 0;

    if (maxlen > 255) maxlen = 255; // Can't put more than 255 in a Pascal String

    utf8TextEncoding = CreateTextEncoding(kTextEncodingUnicodeDefault, kTextEncodingDefaultVariant, kUnicodeUTF8Format);
    UpgradeScriptInfoToTextEncoding(smSystemScript, kTextLanguageDontCare, kTextRegionDontCare, NULL, &SystemTextEncoding);
    theMapping.unicodeEncoding = utf8TextEncoding;
    theMapping.otherEncoding   = SystemTextEncoding;
    theMapping.mappingVersion  = kUnicodeUseLatestMapping;
    status = CreateTextToUnicodeInfo(&theMapping, &textToUnicodeInfo);
    if (status == noErr)
    {
        status = ConvertFromPStringToUnicode(textToUnicodeInfo, *machineName, maxlen, &unicodelen, (UniCharArrayPtr)&(utf8[1]));
        DisposeTextToUnicodeInfo(&textToUnicodeInfo);
    }
    utf8[0] = (UInt8)unicodelen;
    return(status);
}

mDNSlocal void GetUserSpecifiedComputerName(domainlabel *const namelabel)
{
    StringHandle machineName = GetString(-16413);   // Get machine name set in file sharing
    if (machineName)
    {
        char machineNameState = HGetState((Handle)machineName);
        HLock((Handle)machineName);
        ConvertStringHandleToUTF8(machineName, namelabel->c, MAX_DOMAIN_LABEL);
        HSetState((Handle)machineName, machineNameState);
    }
}

#endif

static pascal void mDNSTimerTask(void *arg)
{
#if MDNS_ONLYSYSTEMTASK
#pragma unused(arg)
    ONLYSYSTEMTASKevent = true;
#else
    mDNS *const m = (mDNS *const)arg;
    if (!m->p->ep) LogMsg("mDNSTimerTask NO endpoint");
    if (m->mDNS_busy) LogMsg("mDNS_busy");
    if (m->p->nesting) LogMsg("mDNSTimerTask ERROR! OTEnterNotifier is supposed to suppress timer callbacks too");

    // If our timer fires at a time when we have no endpoint, ignore it --
    // once we reopen our endpoint and get our T_BINDCOMPLETE message we'll call
    // mDNS_RegisterInterface(), which does a lock/unlock, which retriggers the timer.
    // Likewise, if m->mDNS_busy or m->p->nesting, we'll catch this on the unlock
    if (m->p->ep && m->mDNS_busy == 0 && m->p->nesting == 0) mDNS_Execute(m);
#endif
}

#if TEST_SLEEP
long sleep, wake, mode;
#endif

mDNSexport mStatus mDNSPlatformInit (mDNS *const m)
{
    OSStatus err = InitOpenTransport();

    ClientNotifierContext = m;
    // Note: OTRegisterAsClient returns kOTNotSupportedErr when running as Carbon code on OS X
    // -- but that's okay, we don't need a ClientNotifier when running as Carbon code on OS X
    OTRegisterAsClient(NULL, NewOTNotifyUPP(ClientNotifier));

    m->p->OTTimerTask = OTCreateTimerTask(NewOTProcessUPP(mDNSTimerTask), m);
    m->p->nesting     = 0;

#if TEST_SLEEP
    sleep = TickCount() + 600;
    wake = TickCount() + 1200;
    mode = 0;
#endif

    // Set up the nice label
    m->nicelabel.c[0] = 0;
    GetUserSpecifiedComputerName(&m->nicelabel);
//	m->nicelabel = *(domainlabel*)"\pStu";	// For conflict testing
    if (m->nicelabel.c[0] == 0) MakeDomainLabelFromLiteralString(&m->nicelabel, "Macintosh");

    // Set up the RFC 1034-compliant label
    m->hostlabel.c[0] = 0;
    ConvertUTF8PstringToRFC1034HostLabel(m->nicelabel.c, &m->hostlabel);
    if (m->hostlabel.c[0] == 0) MakeDomainLabelFromLiteralString(&m->hostlabel, "Macintosh");

    mDNS_SetFQDN(m);

    // When it's finished mDNSOpenEndpoint asynchronously calls mDNSinitComplete() and then mDNS_RegisterInterface()
    CallmDNSNotifierUPP = NewOTNotifyUPP(CallmDNSNotifier);
    err = mDNSOpenEndpoint(m);
    if (err)
    {
        LogMsg("mDNSOpenEndpoint failed %d", err);
        if (m->p->OTTimerTask) OTDestroyTimerTask(m->p->OTTimerTask);
        OTUnregisterAsClient();
        CloseOpenTransport();
    }
    return(err);
}

extern void mDNSPlatformClose (mDNS *const m)
{
    if (m->p->mOTstate == mOT_Ready)
    {
        m->p->mOTstate = mOT_Closed;
        mDNS_DeregisterInterface(m, &m->p->interface, mDNSfalse);
    }
    if (m->p->ep)          { OTCloseProvider   (m->p->ep);          m->p->ep          = NULL; }
    if (m->p->OTTimerTask) { OTDestroyTimerTask(m->p->OTTimerTask); m->p->OTTimerTask = 0;    }

    OTUnregisterAsClient();
    CloseOpenTransport();
}

#if MDNS_ONLYSYSTEMTASK
extern void mDNSPlatformIdle(mDNS *const m);
mDNSexport void mDNSPlatformIdle(mDNS *const m)
{
    while (ONLYSYSTEMTASKcontextPtr)
    {
        void *contextPtr = ONLYSYSTEMTASKcontextPtr;
        ONLYSYSTEMTASKcontextPtr = NULL;
        mDNSNotifier(contextPtr, ONLYSYSTEMTASKcode, ONLYSYSTEMTASKresult, ONLYSYSTEMTASKcookie);
    }
    if (ONLYSYSTEMTASKevent)
    {
        ONLYSYSTEMTASKevent = false;
        mDNS_Execute(m);
    }

    if (m->p->mOTstate == mOT_Reset)
    {
        printf("\n");
        printf("******************************************************************************\n");
        printf("\n");
        printf("Reopening endpoint\n");
        mDNSOpenEndpoint(m);
        m->ResourceRecords = NULL;
    }

#if TEST_SLEEP
    switch (mode)
    {
    case 0: if ((long)TickCount() - sleep >= 0) { mDNSCoreMachineSleep(m, 1); mode++; }
        break;
    case 1: if ((long)TickCount() - wake >= 0) { mDNSCoreMachineSleep(m, 0); mode++; }
        break;
    }
#endif
}
#endif

mDNSexport void    mDNSPlatformLock(const mDNS *const m)
{
    if (!m) { DebugStr("\pmDNSPlatformLock m NULL!"); return; }
    if (!m->p) { DebugStr("\pmDNSPlatformLock m->p NULL!"); return; }

    // If we try to call OTEnterNotifier and fail because we're already running at
    // Notifier context, then make sure we don't do the matching OTLeaveNotifier() on exit.
    // If we haven't even opened our endpoint yet, then just increment m->p->nesting for the same reason
    if (m->p->mOTstate == mOT_Ready && !m->p->ep) DebugStr("\pmDNSPlatformLock: m->p->mOTstate == mOT_Ready && !m->p->ep");
    if (!m->p->ep || m->p->nesting || OTEnterNotifier(m->p->ep) == false) m->p->nesting++;
}

mDNSlocal void ScheduleNextTimerCallback(const mDNS *const m)
{
    if (m->mDNSPlatformStatus == mStatus_NoError)
    {
        SInt32 interval = m->NextScheduledEvent - mDNS_TimeNow_NoLock(m);
        if      (interval < 1) interval = 1;
        else if (interval > 0x70000000 / 1000) interval = 0x70000000 / mDNSPlatformOneSecond;
        else interval = (interval * 1000 + mDNSPlatformOneSecond-1)/ mDNSPlatformOneSecond;
        OTScheduleTimerTask(m->p->OTTimerTask, (OTTimeout)interval);
    }
}

mDNSexport void    mDNSPlatformUnlock(const mDNS *const m)
{
    if (!m) { DebugStr("\pmDNSPlatformUnlock m NULL!"); return; }
    if (!m->p) { DebugStr("\pmDNSPlatformUnlock m->p NULL!"); return; }

    if (m->p->ep && m->mDNS_busy == 0) ScheduleNextTimerCallback(m);

    if (m->p->nesting) m->p->nesting--;
    else OTLeaveNotifier(m->p->ep);
}

mDNSexport void     mDNSPlatformStrCopy(      void *dst, const void *src)             { OTStrCopy((char*)dst, (char*)src); }
mDNSexport UInt32   mDNSPlatformStrLen (                 const void *src)             { return(OTStrLength((char*)src)); }
mDNSexport void     mDNSPlatformMemCopy(      void *dst, const void *src, UInt32 len) { OTMemcpy(dst, src, len); }
mDNSexport mDNSBool mDNSPlatformMemSame(const void *dst, const void *src, UInt32 len) { return(OTMemcmp(dst, src, len)); }
mDNSexport void     mDNSPlatformMemZero(      void *dst,                  UInt32 len) { OTMemzero(dst, len); }
mDNSexport void *   mDNSPlatformMemAllocate(mDNSu32 len)                              { return(OTAllocMem(len)); }
mDNSexport void     mDNSPlatformMemFree(void *mem)                                    { OTFreeMem(mem); }
mDNSexport mDNSu32  mDNSPlatformRandomSeed(void)                                      { return(TickCount()); }
mDNSexport mStatus  mDNSPlatformTimeInit(void)                                        { return(mStatus_NoError); }
mDNSexport SInt32   mDNSPlatformRawTime()                                             { return((SInt32)TickCount()); }
mDNSexport SInt32 mDNSPlatformOneSecond = 60;

mDNSexport mDNSs32  mDNSPlatformUTC(void)
{
    // Classic Mac OS since Midnight, 1st Jan 1904
    // Standard Unix counts from 1970
    // This value adjusts for the 66 years and 17 leap-days difference
    mDNSu32 SecsSince1904;
    MachineLocation ThisLocation;
    #define TIME_ADJUST (((1970 - 1904) * 365 + 17) * 24 * 60 * 60)
    #define ThisLocationGMTdelta ((ThisLocation.u.gmtDelta << 8) >> 8)
    GetDateTime(&SecsSince1904);
    ReadLocation(&ThisLocation);
    return((mDNSs32)(SecsSince1904 - ThisLocationGMTdelta - TIME_ADJUST));
}
